package form.ui.core
{
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.geom.Matrix;
	
	import form.utils.GraphicsUtil;

	public class ProgrammaticSkin extends Shape
	{
		/**
		 *  @private
		 *  Set by horizontalGradientMatrix() or verticalGradientMatrix().
		 */
		private static var tempMatrix:Matrix = new Matrix();
	
		public function ProgrammaticSkin()
		{
			super();
			_width = measuredWidth;
			_height = measuredHeight;
			
			addEventListener(Event.ADDED_TO_STAGE, onAddToStage, false, 0, true);
		}
				
		private function onAddToStage(e:Event):void
		{
			invalidateDisplayList();
		}
		
		public function invalidateDisplayList():void
		{
			updateDisplayList(_width, _height);
		}
		
		protected function updateDisplayList(w:Number, h:Number):void
		{	
			
		}
		
		/**
		 *  @private
		 *  Storage for the height property.
		 */
		private var _height:Number;
	
		/**
		 *  @private
		 */
		override public function get height():Number
		{
			return _height;
		}
	
		/**
		 *  @private
		 */
		override public function set height(value:Number):void
		{
			if(_height != value)
			{
				_height = value;
				invalidateDisplayList();
			}
			
		}
		
		//----------------------------------
		//  width
		//----------------------------------
	
		/**
		 *  @private
		 *  Storage for the width property.
		 */
		private var _width:Number;
		
		/**
		 *  @private
		 */
		override public function get width():Number
		{
			return _width;
		}
	
		/**
		 *  @private
		 */
		override public function set width(value:Number):void
		{
			if(_width != value)
			{
				_width = value;
				invalidateDisplayList();
			}
		}
		
		//----------------------------------
		//  measuredHeight
		//----------------------------------
	
		/**
		 *  The measured height of this object.
		 *  This should be overridden by subclasses to return the preferred height for
		 *  the skin.
		 *
		 *  @return The measured height of the object, in pixels.
		 */
		public function get measuredHeight():Number
		{
			return 0;
		}
	
		//----------------------------------
		//  measuredWidth
		//----------------------------------
	
		/**
		 *  The measured width of this object.
		 *  This should be overridden by subclasses to return the preferred width for
		 *  the skin.
		 *
		 *  @return The measured width of the object, in pixels.
		 */
		public function get measuredWidth():Number
		{
			return 0;
		}
		
		/**
		 *  Utility function to create a horizontal gradient matrix.
		 *
		 *  @param x The left edge of the gradient.
		 *
		 *  @param y The top edge of the gradient.
		 *
		 *  @param width The width of the gradient.
		 *
		 *  @param height The height of the gradient.
		 *
		 *  @return The horizontal gradient matrix. This is a temporary
		 *  object that should only be used for a single subsequent call
		 *  to the <code>drawRoundRect()</code> method.
		 */
		protected function horizontalGradientMatrix(x:Number, y:Number,
													width:Number,
													height:Number):Matrix
		{
			return rotatedGradientMatrix(x, y, width, height, 0);
		}
		
		/**
		 *  Utility function to create a vertical gradient matrix.
		 *
		 *  @param x The left edge of the gradient.
		 *
		 *  @param y The top edge of the gradient.
		 *
		 *  @param width The width of the gradient.
		 *
		 *  @param height The height of the gradient.
		 *
		 *  @return The horizontal gradient matrix. This is a temporary
		 *  object that should only be used for a single subsequent call
		 *  to the <code>drawRoundRect()</code> method.
		 */
		protected function verticalGradientMatrix(x:Number, y:Number,
												  width:Number,
												  height:Number):Matrix
		{
			return rotatedGradientMatrix(x, y, width, height, 90);
		}
	
		/**
		 *  Utility function to create a rotated gradient matrix.
		 *
		 *  @param x The left edge of the gradient.
		 *
		 *  @param y The top edge of the gradient.
		 *
		 *  @param width The width of the gradient.
		 *
		 *  @param height The height of the gradient.
		 *
		 *  @param rotation The amount to rotate, in degrees.
		 *
		 *  @return The horizontal gradient matrix. This is a temporary
		 *  object that should only be used for a single subsequent call
		 *  to the <code>drawRoundRect()</code> method.
		 */
		protected function rotatedGradientMatrix(x:Number, y:Number,
												 width:Number,
												 height:Number,
												 rotation:Number):Matrix
		{
			tempMatrix.createGradientBox(width, height,
										 rotation * Math.PI / 180, x, y);
			return tempMatrix;
		}
	
		/**
		 *  Programatically draws a rectangle into this skin's Graphics object.
		 *
		 *  <p>The rectangle can have rounded corners.
		 *  Its edges are stroked with the current line style
		 *  of the Graphics object.
		 *  It can have a solid color fill, a gradient fill, or no fill.
		 *  A solid fill can have an alpha transparency.
		 *  A gradient fill can be linear or radial. You can specify
		 *  up to 15 colors and alpha values at specified points along
		 *  the gradient, and you can specify a rotation angle
		 *  or transformation matrix for the gradient.
		 *  Finally, the rectangle can have a rounded rectangular hole
		 *  carved out of it.</p>
		 *
		 *  <p>This versatile rectangle-drawing routine is used by many skins.
		 *  It calls the <code>drawRect()</code> or
		 *  <code>drawRoundRect()</code>
		 *  methods (in the flash.display.Graphics class) to draw into this
		 *  skin's Graphics object.</p>
		 *
		 *	@param x Horizontal position of upper-left corner
		 *  of rectangle within this skin.
		 *
		 *	@param y Vertical position of upper-left corner
		 *  of rectangle within this skin.
		 *
		 *	@param width Width of rectangle, in pixels.
		 *
		 *	@param height Height of rectangle, in pixels.
		 *
		 *	@param cornerRadius Corner radius/radii of rectangle.
		 *  Can be <code>null</code>, a Number, or an Object.
		 *  If it is <code>null</code>, it specifies that the corners should be square
		 *  rather than rounded.
		 *  If it is a Number, it specifies the same radius, in pixels,
		 *  for all four corners.
		 *  If it is an Object, it should have properties named
		 *  <code>tl</code>, <code>tr</code>, <code>bl</code>, and
		 *  <code>br</code>, whose values are Numbers specifying
		 *  the radius, in pixels, for the top left, top right,
		 *  bottom left, and bottom right corners.
		 *  For example, you can pass a plain Object such as
		 *  <code>{ tl: 5, tr: 5, bl: 0, br: 0 }</code>.
		 *  The default value is null (square corners).
		 *
		 *	@param color The RGB color(s) for the fill.
		 *  Can be <code>null</code>, a uint, or an Array.
		 *  If it is <code>null</code>, the rectangle not filled.
		 *  If it is a uint, it specifies an RGB fill color.
		 *  For example, pass <code>0xFF0000</code> to fill with red.
		 *  If it is an Array, it should contain uints
		 *  specifying the gradient colors.
		 *  For example, pass <code>[ 0xFF0000, 0xFFFF00, 0x0000FF ]</code>
		 *  to fill with a red-to-yellow-to-blue gradient.
		 *  You can specify up to 15 colors in the gradient.
		 *  The default value is null (no fill).
		 *
		 *	@param alpha Alpha value(s) for the fill.
		 *  Can be null, a Number, or an Array.
		 *  This argument is ignored if <code>color</code> is null.
		 *  If <code>color</code> is a uint specifying an RGB fill color,
		 *  then <code>alpha</code> should be a Number specifying
		 *  the transparency of the fill, where 0.0 is completely transparent
		 *  and 1.0 is completely opaque.
		 *  You can also pass null instead of 1.0 in this case
		 *  to specify complete opaqueness.
		 *  If <code>color</code> is an Array specifying gradient colors,
		 *  then <code>alpha</code> should be an Array of Numbers, of the
		 *  same length, that specifies the corresponding alpha values
		 *  for the gradient.
		 *  In this case, the default value is <code>null</code> (completely opaque).
		 *
	     *  @param gradientMatrix Matrix object used for the gradient fill. 
	     *  The utility methods <code>horizontalGradientMatrix()</code>, 
	     *  <code>verticalGradientMatrix()</code>, and
	     *  <code>rotatedGradientMatrix()</code> can be used to create the value for 
	     *  this parameter.
		 *
		 *	@param gradientType Type of gradient fill. The possible values are
		 *  <code>GradientType.LINEAR</code> or <code>GradientType.RADIAL</code>.
		 *  (The GradientType class is in the package flash.display.)
		 *
		 *	@param gradientRatios (optional default [0,255])
		 *  Specifies the distribution of colors. The number of entries must match
		 *  the number of colors defined in the <code>color</code> parameter.
		 *  Each value defines the percentage of the width where the color is 
		 *  sampled at 100%. The value 0 represents the left-hand position in 
		 *  the gradient box, and 255 represents the right-hand position in the 
		 *  gradient box. 
		 *
		 *	@param hole (optional) A rounded rectangular hole
		 *  that should be carved out of the middle
		 *  of the otherwise solid rounded rectangle
		 *  { x: #, y: #, w: #, h: #, r: # or { br: #, bl: #, tl: #, tr: # } }
		 *
		 *  @see flash.display.Graphics#beginGradientFill()
		 */
		protected function drawRoundRect(
								x:Number, y:Number, width:Number, height:Number,
								cornerRadius:Object = null,
								color:Object = null,
								alpha:Object = null,
								gradientMatrix:Matrix = null,
								gradientType:String = "linear",
								gradientRatios:Array /* of Number */ = null,
								hole:Object = null):void
		{
			var g:Graphics = graphics;
	
			// Quick exit if weight or height is zero.
			// This happens when scaling a component to a very small value,
			// which then gets rounded to 0.
			if (width == 0 || height == 0)
				return;
	
			// If color is an object then allow for complex fills.
			if (color !== null)
			{
				if (color is uint)
				{
					g.beginFill(uint(color), Number(alpha));
				}
				else if (color is Array)
				{
					var alphas:Array = alpha is Array ?
									   alpha as Array :
									   [ alpha, alpha ];
	
					if (!gradientRatios)
						gradientRatios = [ 0, 0xFF ];
	
					g.beginGradientFill(gradientType,
										color as Array, alphas,
										gradientRatios, gradientMatrix);
				}
			}
	
			var ellipseSize:Number;
	
			// Stroke the rectangle.
			if (!cornerRadius)
			{
				g.drawRect(x, y, width, height);
			}
			else if (cornerRadius is Number)
			{
				ellipseSize = Number(cornerRadius) * 2;
				g.drawRoundRect(x, y, width, height, 
								ellipseSize, ellipseSize);
			}
			else
			{
				GraphicsUtil.drawRoundRectComplex(g,
									   x, y, width, height,
									   cornerRadius.tl, cornerRadius.tr,
									   cornerRadius.bl, cornerRadius.br);
			}
	
			// Carve a rectangular hole out of the middle of the rounded rect.
			if (hole)
			{
				var holeR:Object = hole.r;
				if (holeR is Number)
				{
					ellipseSize = Number(holeR) * 2;
					g.drawRoundRect(hole.x, hole.y, hole.w, hole.h, 
									ellipseSize, ellipseSize);
				}
				else
				{
					GraphicsUtil.drawRoundRectComplex(g,
										   hole.x, hole.y, hole.w, hole.h,
										   holeR.tl, holeR.tr, holeR.bl, holeR.br);
				}	
			}
	
			if (color !== null)
				g.endFill();
		}		
	}
}